﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;

namespace SQLIO_GUI
{

    /*  SQLIO GUI
     *  CREATED BY: JEREMY BEHNE
     *  CREATED ON: August 18th, 2014
     *  PURPOSE: Provides a GUI to run the very usefule SQLIO tool.  
     *  The command line version can be difficult for newcomers, and cumbersome for anyone
     *  who wishes to run several test scenarios.  Typical SQL values are set as the defaults.
     *  
     *  NOTES:  Default threads can be changed in the configuration file, but the thread selection
     *  under test configuration will always override that value.  The purpose is to allow
     *  the same param file to be used for users who like the UI and still want to use the 
     *  command line version from time to time.
     */

    public partial class SQLIO_GUI : Form
    {
        // Initialize an output window, just in case.
        Output o = new Output();

        public SQLIO_GUI()
        {
            InitializeComponent();
        }

        #region Parameter File Configuration

        private void SQLIO_GUI_Load(object sender, EventArgs e)
        {
            // Calls a function to read in the param file.
            LoadConfigurationFile();

            // Set the default selected item for the list boxes.
            lstBlockSize.SelectedIndex = 0;
            lstBuffering.SelectedIndex = 0;
            lstOperation.SelectedIndex = 0;
            lstIOType.SelectedIndex = 0;    
        }

        private void LoadConfigurationFile()
        {
            // Read param file from path, if it exists.
            // Read in file path, threadcount, filesize. (CPU mask is ignored as I never change it.)
            // Check the filename and size to see if they exist already to set initialized = true.
            try
            {
                if (File.Exists(txtSQLIOPath.Text + "\\param.txt"))
                {
                    using (StreamReader read = new StreamReader(txtSQLIOPath.Text + "\\param.txt"))
                    {
                        String strLineIn = read.ReadLine();

                        txtTestFileName.Text = strLineIn.Split(' ')[0];
                        txtDefaultThreads.Text = strLineIn.Split(' ')[1];
                        txtTestFileSize.Text = strLineIn.Split(' ')[3];
                    }

                    // Call function to find if the data file has been created.
                    CheckTestFileInitialized();
                }
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private void CheckTestFileInitialized()
        {
            try
            {
                // First, check to see if the filename exists.
                if (File.Exists(txtTestFileName.Text))
                {
                    // Then check the size of the file.  If it is equal or greater than specified, we are ready to test.
                    FileInfo f = new FileInfo(txtTestFileName.Text);
                    if (f.Length / 1048576 >= long.Parse(txtTestFileSize.Text))
                        txtInitialized.Text = "Yes";
                    
                    // If the file exists but is not large enough, enable the button to call the initialize funtion.
                    else
                        btnInitializeFile.Enabled = true;
                }

                // And if the file did not exist at all, enable the button to call the initialize function.
                else
                    btnInitializeFile.Enabled = true;
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private void btnLoadConfiguration_Click(object sender, EventArgs e)
        {
            // Manually reload the param file.  Useful if changed outside of the app.
            // Reset status info.
            lblConfigurationStatus.Text = "";

            // Set the initialized field back to NO, the LoadConfiguration function will check it again.
            txtInitialized.Text = "No";

            // Call the load function, just like at startup.
            LoadConfigurationFile();
        }

        private void btnInitializeFile_Click(object sender, EventArgs e)
        {
            // This will create a test file in the specified location using the paramaters from the param file.
            try
            {
                // Visually show the app is working.
                btnInitializeFile.Text = "Initializing...";
                btnInitializeFile.BackColor = Color.Red;
                Cursor.Current = Cursors.WaitCursor;
                this.Refresh();

                // Create the process.
                System.Diagnostics.Process process = new System.Diagnostics.Process();

                // Create the process info with the command to execute.  This is only to kickstart the test file build.
                System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo("cmd.exe", "/c cd \"" + txtSQLIOPath.Text + "\" & sqlio -kW -s1 -fsequential -o1 -b64 -LS -Fparam.txt");
                
                // Hide the command window.
                info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;

                // Set the start info and start the process.
                process.StartInfo = info;
                process.Start();

                // Wait for the process the complete before continuing.
                process.WaitForExit();

                // Reset visuals now that the background work is complete.
                Cursor.Current = Cursors.Default;
                btnInitializeFile.Text = "Initialize Test File";
                btnInitializeFile.BackColor = SystemColors.Control;
                btnInitializeFile.Enabled = false;

                // Set the text to YES.
                txtInitialized.Text = "Yes";
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private void btnSaveConfiguration_Click(object sender, EventArgs e)
        {
            // This writes the configuration back out to the specified param file.
            try
            {
                // This will overwrite the entire file each time.
                using (System.IO.StreamWriter file = new System.IO.StreamWriter(txtSQLIOPath.Text + "\\param.txt", false))
                {
                    file.Write(txtTestFileName.Text + " " + txtDefaultThreads.Text + " 0x0 " + txtTestFileSize.Text);
                }

                // Once saved, reset the status and check to see if the file is still initialized for these settings.
                lblConfigurationStatus.Text = "";
                CheckTestFileInitialized();
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private void txtInitialized_TextChanged(object sender, EventArgs e)
        {
            // This controls whether the panel with the test selections is visible.
            // If the test file is not initialized, the tests cannot be run.
            if (txtInitialized.Text.Equals("Yes"))
                groupTestParam.Visible = true;
            else
                groupTestParam.Visible = false;
        }

        private void txtTestFileName_TextChanged(object sender, EventArgs e)
        {
            // This detects changes that affect the test file and prevents tests until everything is checked.
            ConfigurationChangedNotSaved();
        }

        private void txtDefaultThreads_TextChanged(object sender, EventArgs e)
        {
            // This detects changes that affect the test file and prevents tests until everything is checked.
            ConfigurationChangedNotSaved();
        }

        private void txtTestFileSize_TextChanged(object sender, EventArgs e)
        {
            // This detects changes that affect the test file and prevents tests until everything is checked.
            ConfigurationChangedNotSaved();
        }

        private void ConfigurationChangedNotSaved()
        {
            // If the parameters have been changed, but not saved to the file, do not allow tests to run.
            btnInitializeFile.Enabled = false;
            txtInitialized.Text = "No";

            // Visually show that tests cannot be run until configuration is saved.
            lblConfigurationStatus.Text = "Configuration changes must be saved to initialize file.";
        }

        #endregion



        #region Run SQLIO Tests

        private void chkOutputToWindow_CheckedChanged(object sender, EventArgs e)
        {
            // This enables/disables the file output box to show the output will go to file or window.
            if (chkOutputToWindow.Checked)
                txtOutputFilePath.Enabled = false;
            else
                txtOutputFilePath.Enabled = true;
        }

        private void btnBeginTest_Click(object sender, EventArgs e)
        {
            try
            {
                // String for the final command,a placeholder to translate buffer type selected, and output from the command if selected.
                String strCommand = "sqlio", strBufferType = "";

                // -k is the operation type, with a W for writes and R for reads.
                if (lstOperation.SelectedItem.Equals("Write"))
                    strCommand += " -kW";
                else
                    strCommand += " -kR";

                // -t is the thread count to use. (This overrides the default from the param.txt)
                strCommand += " -t" + numericThreadCount.Value;
                
                // -s is the time (in seconds) to run the operation.
                strCommand += " -s" + numericRuntime.Value;

                // -f is the access method used. (MUST be in lower case!)
                // A general guideline is SQL uses random for data files and sequential for log files.
                strCommand += " -f" + lstIOType.SelectedItem.ToString().Split(' ')[0].ToLower();

                // -o is the number of outstanding requests PER THREAD.  
                strCommand += " -o" + numericRequests.Value;

                // -b is the block size to use. SQL Server GENERALLY operates in 64k blocks.
                strCommand += " -b" + lstBlockSize.SelectedItem.ToString().Split('k')[0];
               
                // -B is the buffering type.  SQL Server is usually hardware.
                strBufferType = lstBuffering.SelectedItem.ToString().Split(' ')[0];
                if (strBufferType.Equals("Hardware"))
                    strCommand += " -BH";
                else if (strBufferType.Equals("Software"))
                    strCommand += " -BS";
                else if (strBufferType.Equals("Both"))
                    strCommand += " -BY";
                else if (strBufferType.Equals("None"))
                    strCommand += " -BN";

                // -LS will give more detail and -F is the param file that was created.
                strCommand += " -LS -Fparam.txt";

                // If the user wants output to go to a file this will redirect cmd output to the specified file.
                if (!chkOutputToWindow.Checked)
                    strCommand += " > \"" + txtOutputFilePath.Text + "\"";

                // Set visuals for operation in progress.
                btnBeginTest.Text = "Testing in progress...";
                btnBeginTest.BackColor = Color.Red;
                Cursor.Current = Cursors.WaitCursor;
                this.Refresh();

                // Create the process.
                System.Diagnostics.Process process = new System.Diagnostics.Process();
                
                // Create the process start info with the command that was built.
                System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo("cmd.exe", "/c cd \"" + txtSQLIOPath.Text + "\" & " + strCommand);
                
                // Do not show the command window in the background.
                info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                info.CreateNoWindow = true;
                
                // If the user wants the output to be sent to a window the output must be redirected.
                // An event handler is added to make a call each time output is received.
                // Note - EnableRaisingEvents must be set to true to allow event handling.
                if (chkOutputToWindow.Checked)
                {
                    o.ResetOutputText();
                    info.RedirectStandardOutput = true;
                    info.UseShellExecute = false;
                    process.EnableRaisingEvents = true;
                    process.OutputDataReceived += process_OutputDataReceived;
                }
                
                // Set the startinfo and start the process.
                process.StartInfo = info;
                process.Start();

                // Again, if the user wants the output sent to a window, the beginoutputread must be called.
                if (chkOutputToWindow.Checked)
                    process.BeginOutputReadLine();

                // Wait for the process to complete before continuing, this will leave the app in a "working" state.
                process.WaitForExit();

                // Reset visuals.
                Cursor.Current = Cursors.Default;
                btnBeginTest.Text = "Begin IO Test";
                btnBeginTest.BackColor = SystemColors.Control;

                // Finally, display the output in a window if the user has chosen this option.
                if (chkOutputToWindow.Checked)
                    o.ShowDialog();
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            // This event fires every time a line is written to the output.  
            // Calls a function in the dialog to append the line to the textbox.
            o.AddText(e.Data + "\r\n");
        }

        #endregion
    }
}
